导航菜单
首页 >  管理依赖  > 第五十章. 依赖管理

第五十章. 依赖管理

第五十章. 依赖管理 Chapter 50. Dependency Management 50.1.导言50.1. Introduction

依赖关系管理是每个构建的一个关键特性,Gradle的重点在于提供易于理解且与各种方法兼容的一流的依赖管理。 如果您熟悉Maven或Ivy所使用的方法,那么你会很高兴知道Gradle完全兼容这两种方法,此外,它还具有足够的灵活性以支持完全自定义的方法。 Dependency management is a critical feature of every build, and Gradle has placed an emphasis on offering first-class dependency management that is both easy-to-understand and compatible with a wide variety of approaches. If you are familiar with the approach used by either Maven or Ivy you will be delighted to learn that Gradle is fully compatible with both approaches in addition to being flexible enough to support fully-customized approaches.

以下是 Gradle 支持的依赖管理的主要亮点:Here are the major highlights of Gradle's support for dependency management:

依赖管理传递:Gradle 使你可以完全控制项目的依赖树。

Transitive dependency management: Gradle gives you full control of your project's dependency tree.

对非管理依赖的支持:如果你的依赖只是版本控制下或共享驱动器中的文件,Gradle 也提供了强大的功能来支持这种情况。

Support for non-managed dependencies: If your dependencies are simply files in version control or a shared drive, Gradle provides powerful functionality to support this.

对自定义依赖定义的支持:Gradle 的模块依赖使你能够描述构建脚本中的依赖层次结构。

Support for custom dependency definitions.: Gradle's Module Dependencies give you the ability to describe the dependency hierarchy in the build script.

完全可自定义的依赖解析方法:Gradle 使你能够自定义使依赖替换变得简单的解析规则。

A fully customizable approach to Dependency Resolution: Gradle provides you with the ability to customize resolution rules making dependency substitution easy.

完全兼容 Maven 和 Ivy :如果你已经在 Maven POM 文件或 Ivy 文件中定义了依赖,Gradle 有提供一系列常用的构建工具来进行无缝集成。

Full Compatibility with Maven and Ivy: If you have defined dependencies in a Maven POM or an Ivy file, Gradle provide seamless integration with a range of popular build tools.

与现有依赖管理基础结构的集成:Gradle兼容Maven和Ivy仓库。 如果你是使用Archiva, Nexus或Artifactory,Gradle与所有仓库格式100%兼容。

Integration with existing dependency management infrastructure: Gradle is compatible with both Maven and Ivy repositories. If you use Archiva, Nexus, or Artifactory, Gradle is 100% compatible with all repository formats.

由于成千上万相互依赖的开源组件各有一系列版本和不兼容性,依赖管理常常导致问题的复杂性增加。 当一个构建的依赖树变得笨拙时,你的构建工具不应强制你对依赖管理采取单一、不灵活的方法。 一个正确的构建系统必须设计得灵活,而Gradle可以处理任何情况。 With hundreds of thousands of interdependent open source components each with a range of versions and incompatibilities, dependency management has a habit of causing problems as builds grow in complexity. When a build's dependency tree becomes unwieldy, your build tool shouldn't force you to adopt a single, inflexible approach to dependency management. A proper build system has to be designed to be flexible, and Gradle can handle any situation.

50.1.1. 迁移的灵活依赖管理50.1.1. Flexible dependency management for migrations

在从一个构建系统迁移到另一个构建系统的过程当中,对于依赖管理而方,可能特别具有挑战性。 如果你要从Ant或Maven之类的工具迁移到Gradle,那么可能会面临一些困难的情况。 例如一个常见的模式是Ant项目,其中包含了存储在文件系统中的无版本jar文件。 其他构建系统需要在迁移之前批量替换此方法。而使用Gradle,你可以让新构建调整为任何现有的依赖源或依赖元数据。这使得增量迁移到Gradle的难度比其他的规范方案要容易得多。 在大多数大型项目上,构建迁移以及对开发流程的任何更改都是增量进行的,因为大多数组织都无法停止所有的一切,并迁移到一个构建工具的依赖管理概念中。 Dependency management can be particularly challenging during a migration from one build system to another. If you are migrating from a tool like Ant or Maven to Gradle, you may be faced with some difficult situations. For example, one common pattern is an Ant project with version-less jar files stored in the filesystem. Other build systems require a wholesale replacement of this approach before migrating. With Gradle, you can adapt your new build to any existing source of dependencies or dependency metadata. This makes incremental migration to Gradle much easier than the alternative. On most large projects, build migrations and any change to development process is incremental because most organizations can't afford to stop everything and migrate to a build tool's idea of dependency management.

即使你的项目正在使用自定义的依赖管理系统,或或是一些像Eclipse.classpath文件作为依赖管理的主数据,也很容易编写Gradle插件在Gradle中使用此数据。 出于迁移目的,这是Gradle中的常见技术。(但是,如果你已经迁移,那么可以从.classpath文件中移出并直接使用Gradle的依赖管理功能可能是个好主意。) Even if your project is using a custom dependency management system or something like an Eclipse .classpath file as master data for dependency management, it is very easy to write a Gradle plugin to use this data in Gradle. For migration purposes this is a common technique with Gradle. (But, once you've migrated, it might be a good idea to move away from a .classpath file and use Gradle's dependency management features directly.)

50.1.2. 依赖管理和 Java50.1.2. Dependency management and Java

具有讽刺意味的是,以丰富的开源组件库著称的语言,Java竟然没有库或者版本的概念。在Java中,没有标准的方法来告知JVM你正在使用Hibernate V3.0.5,也没有标准的方法来表示 foo-1.0.jar 依赖于 bar-2.0.jar。 这导致了外部的解决方案通常都会基于构建工具。 目前最受欢迎的解决方案是Maven和Ivy。 Maven提供了完整的构建系统,而Ivy则只着眼于依赖管理。 It is ironic that in a language known for its rich library of open source components that Java has no concept of libraries or versions. In Java, there is no standard way to tell the JVM that you are using version 3.0.5 of Hibernate, and there is no standard way to say that foo-1.0.jar depends on bar-2.0.jar. This has led to external solutions often based on build tools. The most popular ones at the moment are Maven and Ivy. While Maven provides a complete build system, Ivy focuses solely on dependency management.

这两种工具都依赖于描述符XML文件,这些文件包含有关特定jar的依赖信息。 它们还使用仓库,在这些仓库中,实际的jar与它们的描述符文件放在一起;并且这两者都以一种形式或其他形式提供了jar版本冲突的解决方案。它们都成为解决依赖冲突的标准,而Gradle对于依赖管理从一开始在底层上使用的是Ivy。Gradle取代了对Ivy的直接依赖,采用了本地Gradle语解决引擎,该引擎支持一系列依赖解决方案的方法,包括POM和Ivy描述符文件。 Both tools rely on descriptor XML files, which contain information about the dependencies of a particular jar. Both also use repositories where the actual jars are placed together with their descriptor files, and both offer resolution for conflicting jar versions in one form or the other. Both have emerged as standards for solving dependency conflicts, and while Gradle originally used Ivy under the hood for its dependency management. Gradle has replaced this direct dependency on Ivy with a native Gradle dependency resolution engine which supports a range of approaches to dependency resolution including both POM and Ivy descriptor files.

50.2. 依赖管理最佳实践50.2. Dependency Management Best Practices

由于Gradle在依赖管理方面有强烈的主张,该工具提供了两个选项让你从中选择:遵循推荐的最佳实践,或支持你可以想到的任何类型的模式。 本节概述Gradle项目建议的用于管理依赖的最佳实践。 While Gradle has strong opinions on dependency management, the tool gives you a choice between two options: follow recommended best practices or support any kind of pattern you can think of. This section outlines the Gradle project's recommended best practices for managing dependencies.

无论哪种语言,适当的依赖管理对于每个项目都很重要。从一个由Java编写的依赖数百个开源库的复杂企业应用,到依赖少数几个库的最简单的Clojure应用,依赖管理的方法大不相同,并且可能取决于目标技术、应用程序部署的方法以及项目的性质。多上项目捆绑为可复用的库,比起企业应用集成到更大规模的软件和基础结构系统中,可能有不同的需求。尽管需求的差异很大,Gradle项目建议所有项目都遵循这组核心规则: No matter what the language, proper dependency management is important for every project. From a complex enterprise application written in Java depending on hundreds of open source libraries to the simplest Clojure application depending on a handful of libraries, approaches to dependency management vary widely and can depend on the target technology, the method of application deployment, and the nature of the project. Projects bundled as reusable libraries may have different requirements than enterprise applications integrated into much larger systems of software and infrastructure. Despite this wide variation of requirements, the Gradle project recommends that all projects follow this set of core rules:

50.2.1. 在文件名中包含版本号(版本化 jar )50.2.1. Put the Version in the Filename (Version the jar)

在文件名中库的版本必须是容易辨认的。虽然jar的版本通常在Manifest文件中,但当你要检查项目时它并不显而易见。如果有人让你看20个jar文件,你更愿意哪一种?名字像 commons-beanutils-1.3.jar 的文件集还是名字像 spring.jar的文件集?如果依赖的文件名称带有版本号,那么将更容易快速确定依赖的版本。 The version of a library must be easy to recognize in the filename. While the version of a jar is usually in the Manifest file, it isn't readily apparent when you are inspecting a project. If someone asks you to look at a collection of 20 jar files, which would you prefer? A collection of files with names like commons-beanutils-1.3.jar or a collection of files with names like spring.jar? If dependencies have file names with version numbers it is much easier to quickly identify the versions of your dependencies.

如果版本不清楚,你可能会引入一些很难找以的微妙错误。例如可能有一个项目使用 Hibernate 2.5,想一下一个开发者决定在她的机器上安装 3.0.5 的版本,以修复一个关键的安全 bug,但她忘记通知其他团队这个变化。她可能成功地解决了这个安全 bug,但她也可能引入一些 bug 到代码库中,如项目用到了 Hibernate 现在弃用的功能。一周后,在集成的机器上可能会有一个异常,而这个异常无法在任何人的机器上复现。然后多个开发者花了数天的时间去查这个问题,最终才意识到,如果他们知道 Hibernate 已经从 2.5 升级到 3.0.5,这个错误会很容易发现。 If versions are unclear you can introduce subtle bugs which are very hard to find. For example there might be a project which uses Hibernate 2.5. Think about a developer who decides to install version 3.0.5 of Hibernate on her machine to fix a critical security bug but forgets to notify others in the team of this change. She may address the security bug successfully, but she also may have introduced subtle bugs into a codebase that was using a now-deprecated feature from Hibernate. Weeks later there is an exception on the integration machine which can't be reproduced on anyone's machine. Multiple developers then spend days on this issue only finally realising that the error would have easy to uncover if they knew that Hibernate had been upgraded from 2.5 to 3.0.5.

在 jar 名称中的版本增强了项目的表现性,并使其更易于维护。这种做法也减少了发生错误的可能。 Versions in jar names increase the expressiveness of your project and make them easier to maintain. This practice also reduces the potential for error.

50.2.2. 管理传递依赖50.2.2. Manage transitive dependencies

传递依赖管理是一种使你的项目依赖于一些库,而这些库又依赖于其他库技术。这种传递依赖的递归模式导致的结果说,在依赖树中,会包含项目的第一级依赖,第二级依赖,等等。如果你不按层级树的第一级和二级依赖对你的依赖建模,那么就会很容易失去对组合的非结构化依赖的控制。请考虑Gradle项目本身,而Gradle仅具有几个直接的第一级依赖,当编译Gradle时,在它的类路径上会需要超过一百个依赖。在规模更大的范围内,使用Spring、Hibernate和其他库,以及成百上千的内部项目的企业项目,可能有非常大的依赖树。 Transitive dependency management is a technique that enables your project to depend on libraries which, in turn, depend on other libraries. This recursive pattern of transitive dependencies results in a tree of dependencies including your project's first-level dependencies, second-level dependencies, and so on. If you don't model your dependencies as a hierarchical tree of first-level and second-level dependencies it is very easy to quickly lose control over an assembled mess of unstructured dependencies. Consider the Gradle project itself, while Gradle only has a few direct, first-level dependencies, when Gradle is compiled it needs more that one hundred dependencies on the classpath. On a far larger scale, Enterprise projects using Spring, Hibernate, and other libraries, alongside hundreds or thousands of internal projects can have very large dependency trees.

当这些大的依赖树需要更改时,你通常需要解决某些依赖的版本冲突。比如说某个开源代码库需要一个版本的日志记录库,而另一个需要另一个版本。 Gradle和其他构建工具都能够解决这种依赖树并解决冲突,但不同的是,Gradle使你可以控制传递依赖冲突解决。 When these large dependency trees need to change, you'll often have to solve some dependency version conflicts. Say one open source library needs one version of a logging library and a another uses an alternative version. Gradle and other build tools all have the ability to solve this dependency tree and resolve conflicts, but what differentiates Gradle is the control it gives you over transitive dependencies and conflict resolution.

虽然你可以尝试手动管理此问题,但你很快就会发现此方法不能扩展。 如果你要去掉第一级依赖,你确实不能确定还有其他哪些 jar 是你需要移除的。第一级依赖的依赖本身也可能是第一级依赖,或者是另一个第一级依赖的传递依赖。 如果你想自己管理传递依赖,最终的结局是你的构建会变得很脆弱:没人敢更改你的依赖,因为破坏构建的风险太高了。 项目类路径将变得完全混乱,如果发生类路径问题,那简直就是人间地狱。 While you could try to manage this problem manually, you will quickly find that this approach doesn't scale. If you want to get rid of a first level dependency you really can't be sure which other jars you should remove. A dependency of a first level dependency might also be a first level dependency itself, or it might be a transitive dependency of yet another first level dependency. If you try to manage transitive dependencies yourself, the end of the story is that your build becomes brittle: no one dares to change your dependencies because the risk of breaking the build is too high. The project classpath becomes a complete mess, and, if a classpath problem arises, hell on earth invites you for a ride.

注:NOTE:在一个项目中,我们在类路径中找到了一个神秘的 LDAP 相关的 jar。 没有代码引用此JAR,并且该 jar 包也没有与项目有任何连接。 没人知道该 jar 的用途,直到它从构建中去除,并且应用程序在尝试向 LDAP 进行认证时遇到了严重的性能问题。 这个神秘的 jar 是一个必需传递的第四级依赖,很容易被忽略,因为没有人会费心去使用受管的传递依赖。

Gradle为你提供了不同的表达第一级和传递依赖的方法。 通过Gradle,你可以混合使用和适配一些方法;例如,你可以在 SCM 中存储 jar而不需要 XML描述符文件,并且仍然使用传递依赖管理。 Gradle offers you different ways to express first-level and transitive dependencies. With Gradle you can mix and match approaches; for example, you could store your jars in an SCM without XML descriptor files and still use transitive dependency management.

50.2.3. 解决版本冲突50.2.3. Resolve version conflicts

相同的jar的冲突版本应该被检测到,并且要么解决要么抛出异常。 如果不使用传递依赖管理,版本冲突没被检测到,那以在类路径中的无法预测的顺序将决定最终使用哪个版本的依赖。在有许多开发者更改依赖的大型项目上,成功的构建将会少之又少,因为依赖的顺序可能直接影响构建是否成功(或者在生产中是否出现错误)。 Conflicting versions of the same jar should be detected and either resolved or cause an exception. If you don't use transitive dependency management, version conflicts are undetected and the often accidental order of the classpath will determine what version of a dependency will win. On a large project with many developers changing dependencies, successful builds will be few and far between as the order of dependencies may directly affect whether a build succeeds or fails (or whether a bug appears or disappears in production).

如果你还没有处理过在类路径中 jar 包版本冲突的麻烦,这里有一个小趣闻等着你。在一个有30个子模块的大型项目中,向子项目添加的一个依赖改变了类路径的顺序,Spring 2.5与老的2.4版本 顺序被调换了。虽然可以继续构建,开发者会开始注意到在生产中出现了各种令人惊讶(和惊人可怕)的bug。然而,更糟糕的是,无意降低版本的Spring向系统引入了几个安全缺陷,现在需要整个组织进行全面的安全审计。 If you haven't had to deal with the curse of conflicting versions of jars on a classpath, here is a small anecdote of the fun that awaits you. In a large project with 30 submodules, adding a dependency to a subproject changed the order of a classpath, swapping Spring 2.5 for an older 2.4 version. While the build continued to work, developers were starting to notice all sorts of surprising (and surprisingly awful) bugs in production. Worse yet, this unintentional downgrade of Spring introduced several security vulnerabilities into the system, which now required a full security audit throughout the organization.

简而言之,版本冲突是很不好的,你应该管理您的传递依赖以避免版本冲突。你可能还希望了解使用冲突版本的位置,并在整个组织中统一依赖的指定版本。有了类似于Gradle的良好冲突报告工具,这些信息可 用于与整个组织进行通信,并在单个版本上实现标准化。如果你觉得你不会发生版本冲突,那就再想想。不同的第一级依赖,依赖于其他依赖的不同重叠范围的版本非常常见,并且JVM还不能提供简单的方法,使是能在类路径中让相同的 jar 包可以有不同的版本(请参阅 《第50.1.2节,“依赖管理与Java”)。 In short, version conflicts are bad, and you should manage your transitive dependencies to avoid them. You might also want to learn where conflicting versions are used and consolidate on a particular version of a dependency across your organization. With a good conflict reporting tool like Gradle, that information can be used to communicate with the entire organization and standardize on a single version. If you think version conflicts don't happen to you, think again. It is very common for different first-level dependencies to rely on a range of different overlapping versions for other dependencies, and the JVM doesn't yet offer an easy way to have different versions of the same jar in the classpath (see Section 50.1.2, “Dependency management and Java”).

Gradle 提供了以下的冲突解决策略:Gradle offers the following conflict resolution strategies:

Newest:将使用最新版本的依赖。 这是Gradle的默认策略,只要版本都能向后兼容,通常是合适的选择。 Newest: The newest version of the dependency is used. This is Gradle's default strategy, and is often an appropriate choice as long as versions are backwards-compatible.Fail:版本冲突导致构建失败。这种策略强制在构建脚本中显示地解决所有版本冲突。有关如何显式选择特定版本的详细信息,请参阅ResolutionStrategy。 Fail: A version conflict results in a build failure. This strategy enforces that all version conflicts are resolved explicitly in the build script. See ResolutionStrategy for details on how to explicitly choose a particular version.

虽然上面介绍的策略通常足够解决大部分的冲突,但是 Gradle 也提供了更细粒度的机制来解决版本冲突:While the strategies introduced above are usually enough to solve most conflicts, Gradle provides more fine-grained mechanisms to resolve version conflicts:

将第一级依赖配置为强制。如果冲突中的依赖已经是第一级依赖,那么这一方法会很有用。请参阅 DependencyHandler中的示例。 Configuring a first level dependency as forced. This approach is useful if the dependency in conflict is already a first level dependency. See examples in DependencyHandler.将任何依赖(传递依赖或非传递依赖)配置为强制。 如果冲突中的依赖是传递依赖,那么这种方法会很有用。 它还可用于强制第一级依赖的版本。请参阅 ResolutionStrategy中的示例。Configuring any dependency (transitive or not) as forced. This approach is useful if the dependency in conflict is a transitive dependency. It also can be used to force versions of first level dependencies. See examples in ResolutionStrategy依赖解析规则是一个在 Gradle 1.4 引进的实验性功能,让你可以对特定的依赖选择的版本进行细粒度控制。 Dependency resolve rules are an incubating feature introduced in Gradle 1.4 which give you fine-grained control over the version selected for a particular dependency.

为了解决版本冲突的问题,带有依赖关系图的报告也是很有帮助的。这种报告是依赖管理的另一个功能。To deal with problems due to version conflicts, reports with dependency graphs are also very helpful. Such reports are another feature of dependency management.

50.2.4. 使用动态版本和变化模块50.2.4. Use Dynamic Versions and Changing Modules

当您想要使用特定依赖的最新版本或某个版本范围内的最新版本时,有许多情况。这可能是开发期中需要,或者可能你正在开发一个库,它被设计为使用某个范围内的依赖版本。你可以通过使用动态版本很容易地依赖这些不断变化的依赖。动态版本可以是一个版本范围(例如2.+),也可以是最新版本的占位符(比如latest.integration)。 There are many situation when you want to use the latest version of a particular dependency, or the latest in a range of versions. This can be a requirement during development, or you may be developing a library that is designed to work with a range of dependency versions. You can easily depend on these constantly changing dependencies by using a dynamic version. A dynamic version can be either a version range (e.g. 2.+) or it can be a placeholder for the latest version available (e.g. latest.integration).

或者,有时你请求的模块可能会随着时间的推移而变化,即使是版本相同。这种变化模块类型的一个例子是Maven的SNAPSHOT模块,它始终指向最新发布的工件。换句话说,一个标准的Maven快照是一个永远不会不变的模块,可以这样说,它是一个“变化模块”。 Alternatively, sometimes the module you request can change over time, even for the same version. An example of this type of changing module is a Maven SNAPSHOT module, which always points at the latest artifact published. In other words, a standard Maven snapshot is a module that never stands still so to speak, it is a "changing module".

动态版本 和 变化模块 之间的主要差别是,当你解析一个 动态版本时,你会得到真实的、静态的版本作为模块名称。当你解析一个 变化模块时,将使用你请求的版本来命名工件,但底层工件可能会随时间而变化。 The main difference between a dynamic version and a changing module is that when you resolve a dynamic version, you'll get the real, static version as the module name. When you resolve a changing module, the artifacts are named using the version you requested, but the underlying artifacts may change over time.

默认情况下,Grdale 对动态版本和变化模块的缓存时间是24小时。你可能使用命令行选项重写默认的缓存模式。你可以通过 resolution strategy 修改你的构建的缓存到期时间(见第 50.9.3 节,《调整控制依赖缓存》)。 By default, Gradle caches dynamic versions and changing modules for 24 hours. You can override the default cache modes using command line options. You can change the cache expiry times in your build using the resolution strategy (see Section 50.9.3, “Fine-tuned control over dependency caching”).

50.3. 依赖配置50.3. Dependency configurations

在Gradle中,依赖将被分组到配置。配置有一个名称和许多其他属性,并且可以相互扩展。许多Gradle插件会向项目添加了预定义的配置。例如,Java插件添加了一些配置来表示它需要的各种类路径。有关详细信息,请参阅 第23.5节,《依赖管理》 。当然,你可以在此基础上添加自定义配置。有关自定义配置的用例很多。这是非常方便的,例如添加依赖时不需要再去构建或测试你的软件(比如,要随分发一起提供的其他JDBC驱动程序)。 In Gradle dependencies are grouped into configurations. Configurations have a name, a number of other properties, and they can extend each other. Many Gradle plugin add pre-defined configurations to your project. The Java plugin, for example, adds some configurations to represent the various classpaths it needs. see Section 23.5, “Dependency management” for details. Of course you can add custom configurations on top of that. There are many use cases for custom configurations. This is very handy for example for adding dependencies not needed for building or testing your software (e.g. additional JDBC drivers to be shipped with your distribution).

项目的配置由 configurations 对象管理。你传给这个配置对象的闭包将通过它对应的 API 被应用。要了解有关此 API 的

相关推荐: